﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using System.Windows.Forms.DataVisualization.Charting;
using MacomberMapSystem.Common.Database;
using System.Data.Common;
using MacomberMapSystem.Common.Internals;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using System.Xml;
using System.Reflection;

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// This class produces a one-line report
    /// </summary>
    public class CIM_OneLine_Report : CIM_Processor
    {

        #region Variable declarations
        /// <summary>Our event for the run completing, including HTML output</summary>
        public event ReportCompletionDelegate RunCompletion;

        /// <summary>Our delegate for handling run compltion</summary>
        /// <param name="OutputHtml"></param>
        public delegate void ReportCompletionDelegate(String OutputHtml);

        /// <summary>
        /// Report our process name
        /// </summary>
        public override string ProcessName { get { return "One-Line Report"; } }
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new one-line report
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="NamedParameters"></param>
        /// <param name="Repository"></param>
        public CIM_OneLine_Report(XmlElement xElem, Dictionary<String, Object> NamedParameters, MM_Repository Repository)
            : base(xElem, NamedParameters, Repository)
        { }
        #endregion

        #region Execution
        /// <summary>
        /// Initiate our process
        /// </summary>
        /// <param name="DbConn"></param>
        /// <returns></returns>
        public override CIM_Processor.enumProcessState InitiateProcess(MM_Database_Connector DbConn)
        {
            ProcessLink.Title = "Loading user list";
            List<Chart> GraphsToDraw = new List<Chart>(0);

            //Initialize our dictionary
            TallyDictionary NewDic = new TallyDictionary();
            foreach (MM_Database_User User in Repository.User_Collection.Values)
                NewDic.AddUser(User);

            //Pull in our model categories
            ProcessLink.Title = "Loading model categories";
            foreach (MM_Database_Model Model in Repository.Model_Collection.Values)
                if (!NewDic.LatestModels.ContainsKey(Model.ModelCategory))
                    NewDic.LatestModels.Add(Model.ModelCategory, Model);


            //Go user-by-user, pulling in our counts
            ProcessLink.Title = "Counting assignments in database";
            using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT COUNT(*) FROM MM_DATABASE_ASSIGNMENT"))
                Total = Convert.ToSingle(dCmd.ExecuteScalar());

            //Create a model to handle our Genmaps.
            MM_Database_Model GenmapModel = new MM_Database_Model();
            GenmapModel.ModelCategory="Non-linked";
            GenmapModel.Name="Non-Linked";
            GenmapModel.FullClass = "CIM_NonLinked";
            GenmapModel.FullPath = "(none)";
            GenmapModel.ModelClass = "CIM_NonLinked";
                
            if (Repository.Model_Collection.ContainsKey(-1))
                Repository.Model_Collection[-1] = GenmapModel;
            else
                Repository.Model_Collection.Add(-1, GenmapModel);


            DateTime MinDate = DateTime.MaxValue, MaxDate = DateTime.MinValue;
            ProcessLink.Title = "Loading assignments";
            using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT ASSIGNEDTO, ASSIGNEDON, COMPLETEDON, COMPLETEDBY, ERRORS, WARNINGS, MODEL, NOTES FROM MM_DATABASE_ASSIGNMENT"))
            using (DbDataReader dRd = dCmd.ExecuteReader())
                while (dRd.Read())
                {
                    Current++;
                    //Determine the date of assignment and associated model
                    DateTime AssignedOn = Convert.ToDateTime(dRd["ASSIGNEDON"]);
                    MM_Database_Model CurModel = Repository.Model_Collection[Convert.ToInt32(dRd["Model"])];

                    //Check for our latest models
                    if (!NewDic.LatestModels.ContainsKey(CurModel.ModelCategory))
                        NewDic.LatestModels.Add(CurModel.ModelCategory, CurModel);
                    else if (NewDic.LatestModels[CurModel.ModelCategory].ValidStart < CurModel.ValidStart)
                        NewDic.LatestModels[CurModel.ModelCategory] = CurModel;

                    //If we're completed, also add the completion information
                    NewDic.AddAssignment(Repository.User_Collection[Convert.ToInt32(dRd["ASSIGNEDTO"])], CurModel, Convert.ToInt32(dRd["Errors"]), Convert.ToInt32(dRd["Warnings"]), Convert.ToDateTime(dRd["ASSIGNEDON"]));
                    if (dRd["COMPLETEDON"] is DBNull == false)
                    {
                        DateTime CD = Convert.ToDateTime(dRd["COMPLETEDON"]).Date;
                        MM_Database_User FoundUser;
                        if (!Repository.User_Collection.TryGetValue(Convert.ToInt32(dRd["COMPLETEDBY"]), out FoundUser))
                        {}

                        NewDic.AddCompletion(FoundUser, CurModel, Convert.ToInt32(dRd["Errors"]), Convert.ToInt32(dRd["Warnings"]), CD);
                        if (CD < MinDate)
                            MinDate = CD;
                        if (CD > MaxDate)
                            MaxDate = CD;
                    }

                    //Check for a new substation one
                    if (dRd["NOTES"].ToString().StartsWith("Substation"))
                        NewDic.AddMonthlySummary(CurModel, AssignedOn, TallyDictionary.enumOneLineSummary.New_One_Lines);
                }


            //Now, go through our one-lines, and pull in our list of one-lines where a future one saved the real-time one:
            ProcessLink.Title = "Loading models associated with one-lines";
            using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT MODELS FROM MM_DATABASE_ONELINE"))
            using (DbDataReader dRd = dCmd.ExecuteReader())
                while (dRd.Read())
                {
                    List<MM_Database_Model> Models = new List<MM_Database_Model>();
                    foreach (string str in dRd[0].ToString().Split(','))
                        if (!String.IsNullOrEmpty(str) && str[0] != '-')
                            Models.Add(Repository.Model_Collection[Convert.ToInt32(str)]);
                    if (Models.Count > 1 && Models[0].ModelCategory == "OE Future")
                        for (int a = 1; a < Models.Count; a++)
                            if (Models[a].ModelCategory == "Current")
                            {
                                NewDic.AddMonthlySummary(Models[0], Models[0].ExportedOn, TallyDictionary.enumOneLineSummary.One_Lines_Completed_InFuture_Count);
                                break;
                            }
                }




            ProcessLink.Title = "Generating graphs";

            GraphsToDraw.Add(GeneratePieChart(NewDic, TallyDictionary.enumOneLineCategory.Repaired_OneLines_Total, TallyDictionary.enumOneLineCategory.Assigned_OneLines_Total, "Completed One-Lines", 1063, 623));
            GraphsToDraw.Add(GenerateTimeSeries(NewDic, TallyDictionary.enumOneLineCategory.Repaired_OneLines_Today, TallyDictionary.enumOneLineCategory.Repaired_OneLines_Total, "Completed One-Lines", 1063, 623, MinDate, MaxDate));
            GraphsToDraw.Add(GeneratePieChart(NewDic, TallyDictionary.enumOneLineCategory.Repaired_Fixes_Total, TallyDictionary.enumOneLineCategory.Assigned_Fixes_Total, "Repaired issues", 1063, 623));
            GraphsToDraw.Add(GenerateTimeSeries(NewDic, TallyDictionary.enumOneLineCategory.Repaired_Fixes_Today, TallyDictionary.enumOneLineCategory.Repaired_Fixes_Total, "Repaired issues", 1063, 623, MinDate, MaxDate));
            GraphsToDraw.Add(GenerateStackedChart(NewDic, "One-line errors and warnings", 1063, 623, TallyDictionary.enumOneLineCategory.Total_Errors, Color.Red, TallyDictionary.enumOneLineCategory.Total_Warnings, Color.Yellow));


            //Now, write out our report
            ProcessLink.Title = "Writing HTML output";

            StringBuilder sB = new StringBuilder();
            sB.AppendLine("<!DOCTYPE html PUBLIC \"-//W3C/DTD HTML 4.01 Transitional//EN\">");
            sB.AppendLine("<HTML>");
            sB.AppendLine("<TITLE>NMMS One-Line Report</TITLE>");
            sB.AppendLine("<BODY>");
            sB.AppendLine(String.Format("<B>This is the {0} running on {1} by {2}\\{3} at {4},</B><BR />", Assembly.GetExecutingAssembly().GetName().Name, Environment.MachineName, Environment.UserDomainName, Environment.UserName, DateTime.Now));
            sB.AppendLine("<BR />");
            sB.AppendLine(String.Format("<B>Report on one-line completion against database {0}</B><BR />", Repository.Db.Database.ConnectionString));
            sB.AppendLine("Note: \"Today\" is defined as being within 24 hours from the time this program ran.<BR />");
            sB.AppendLine("<HR />");
            sB.AppendLine("<B>Most recent models:</B><br />");
            foreach (KeyValuePair<String, MM_Database_Model> kvp in NewDic.LatestModels)
                sB.AppendLine(string.Format("{0}: {1}<br/>", kvp.Key, kvp.Value.Name));
            sB.AppendLine("<HR />");


            //Write out our table for summary
            int FoundNum;
            sB.AppendLine("<h2>One-Line assigment summary</h2>");
            sB.AppendLine("<TABLE>");
            sB.AppendLine("<TBODY>");
            sB.Append("<TR><TH align=\"center\">User</TH><TH>&nbsp;</TH>");
            foreach (string Category in Enum.GetNames(typeof(TallyDictionary.enumOneLineCategory)))
                sB.Append("<TH align=\"center\">" + Category.Replace('_', ' ') + "</TH>");
            sB.Append("</TR>");
            foreach (KeyValuePair<MM_Database_User, Dictionary<TallyDictionary.enumOneLineCategory, int>> kvp in NewDic.FixesByUser)
                if (kvp.Value[TallyDictionary.enumOneLineCategory.Assigned_OneLines_Total] + kvp.Value[TallyDictionary.enumOneLineCategory.Repaired_OneLines_Total] > 0)
                {
                    sB.Append("<TR><TH>" + kvp.Key.Nickname + "</TH><TD>&nbsp;</TD>");
                    foreach (TallyDictionary.enumOneLineCategory Category in Enum.GetValues(typeof(TallyDictionary.enumOneLineCategory)))
                        if (!kvp.Value.TryGetValue(Category, out FoundNum) || FoundNum == 0)
                            sB.Append("<TD>&nbsp;</TD>");
                        else
                            sB.Append("<TD align=\"center\">" + FoundNum.ToString("#,##0") + "</TD>");
                    sB.AppendLine("</TR>");
                }
            sB.AppendLine("</TR>");
            sB.AppendLine("</TBODY></TABLE>");
            sB.AppendLine("<HR />");

            List<String> Colors = new List<string>();
            Colors.Add("#0000FF");
            Colors.Add("#FF0000");
            Colors.Add("#FF00FF");

            int[] FoundInts;
            int CurColor = 0;
            sB.AppendLine("<h2>One-Line completion summary</h2>");
            sB.Append("Model categories: ");
            foreach (String str in NewDic.LatestModels.Keys)
                sB.AppendLine("<span style=\"color:" + Colors[CurColor++] + "\">" + str + "</span>&nbsp;");

            List<MM_Database_User> Users = new List<MM_Database_User>(Repository.User_Collection.Values);
            Users.Add(NewDic.Total);

            sB.AppendLine("<TABLE>");
            sB.AppendLine("<TBODY>");
            sB.Append("<TR><TH align=\"center\">User</TH><TH>&nbsp;</TH>");
            List<DateTime> Dates = new List<DateTime>(NewDic.FixesByMonth.Keys);
            foreach (DateTime dt in NewDic.FixesByMonth.Keys)
                sB.Append("<TH align=\"center\">" + dt.ToString("MMM\"<br/>\"yyyy") + "</TH>");
            sB.Append("</TR>");
            foreach (MM_Database_User User in Users)
                if (NewDic.FixesByUser[User][TallyDictionary.enumOneLineCategory.Repaired_OneLines_Total] > 0)
                    for (int CurModel = 0; CurModel < NewDic.LatestModels.Count; CurModel++)
                    {
                        if (CurModel == 0)
                            sB.Append("<TR><TH rowspan=\"" + NewDic.LatestModels.Count.ToString() + "\">" + User.Nickname + "</TH><TD>&nbsp;</TD>");
                        else
                            sB.Append("<TR><TD>&nbsp;</TD>");

                        foreach (DateTime dt in Dates)
                            if (!NewDic.FixesByMonth[dt].TryGetValue(User, out FoundInts) || FoundInts[CurModel * 3] == 0)
                                sB.Append("<TD>&nbsp;</TD>");
                            else
                                sB.Append("<TD align=\"center\" style=\"color:" + Colors[CurModel] + "\">" + FoundInts[CurModel * 3].ToString("#,##0") + "</TD>");
                    }

            sB.AppendLine("</TR>");
            sB.AppendLine("</TBODY></TABLE>");
            sB.AppendLine("<HR />");

            //Write out our summary information
            sB.AppendLine("<h2>One-Line summary information</h2>");
            sB.AppendLine("<TABLE>");
            sB.AppendLine("<TBODY>");
            sB.Append("<TR><TH align=\"center\">Category</TH><TH>&nbsp;</TH>");
            foreach (DateTime dt in NewDic.MonthlySummary.Keys)
                sB.Append("<TH align=\"center\">" + dt.ToString("MMM\"<br/>\"yyyy") + "</TH>");
            sB.Append("</TR>");
            int FoundVal;
            foreach (TallyDictionary.enumOneLineSummary Summary in Enum.GetValues(typeof(TallyDictionary.enumOneLineSummary)))
            {
                sB.AppendLine("<TR><TH>" + Summary.ToString().Replace('_', ' ') + "</TH><TD>&nbsp;</TD>");
                foreach (KeyValuePair<DateTime, Dictionary<TallyDictionary.enumOneLineSummary, int>> kvp in NewDic.MonthlySummary)
                    if (kvp.Value.TryGetValue(Summary, out FoundVal))
                        sB.Append("<TD>" + FoundVal.ToString("#,##0") + "</TD>");
                    else
                        sB.Append("<TD>&nbsp;</TD>");
                sB.AppendLine("</TR>");
            }
            sB.AppendLine("</TBODY></TABLE>");
            sB.AppendLine("<HR />");

            //Now, add in our images

            for (int a = 0; a < GraphsToDraw.Count; a++)
                using (MemoryStream mS = new MemoryStream())
                {
                    GraphsToDraw[a].SaveImage(mS, ImageFormat.Png);
                    mS.Flush();
                    mS.Position = 0;
                    sB.Append("<hr /><img alt=\"Chart " + a.ToString() + "\" src=\"data:image/png;base64," + Convert.ToBase64String(mS.ToArray()) + "\"/>");
                }
            sB.AppendLine("</BODY></HTML>");


            if (NamedParameters.ContainsKey("Output"))
                NamedParameters["Output"] = sB.ToString();
            else
                NamedParameters.Add("Output", sB.ToString());

            if (RunCompletion != null)
                RunCompletion(sB.ToString());
            return enumProcessState.Completed;
        }
        #endregion

        #region Image/text rendering routines

        /// <summary>
        /// Draw our stacked chart
        /// </summary>
        /// <param name="NewDic"></param>
        /// <param name="Title"></param>
        /// <param name="Width"></param>
        /// <param name="Height"></param>
        /// <param name="DrawParams"></param>
        /// <returns></returns>
        private static Chart GenerateStackedChart(TallyDictionary NewDic, String Title, int Width, int Height, params object[] DrawParams)
        {
            Chart StackedBar = new Chart();
            //StackedBar.Palette = ChartColorPalette.Bright;
            StackedBar.Size = new Size(1063, 623);
            StackedBar.Series.Clear();
            StackedBar.ChartAreas.Add(new ChartArea());
            StackedBar.ChartAreas[0].Area3DStyle.Enable3D = true;
            StackedBar.ChartAreas[0].Area3DStyle.IsRightAngleAxes = false;
            StackedBar.ChartAreas[0].Area3DStyle.Inclination = 40;
            StackedBar.ChartAreas[0].Area3DStyle.Rotation = 20;
            StackedBar.ChartAreas[0].Area3DStyle.LightStyle = LightStyle.Realistic;
            StackedBar.Titles.Add(Title).Visible = true;


            for (int a = 0; a < DrawParams.Length; a += 2)
            {
                TallyDictionary.enumOneLineCategory Category = (TallyDictionary.enumOneLineCategory)DrawParams[a];
                Series BarSeries = StackedBar.Series.Add(Category.ToString().Replace('_', ' '));
                BarSeries.ChartType = SeriesChartType.StackedColumn;
                BarSeries.Color = (Color)DrawParams[a + 1];
                BarSeries.XValueType = ChartValueType.DateTime;
                foreach (KeyValuePair<MM_Database_Model, Dictionary<TallyDictionary.enumOneLineCategory, int>> kvp in NewDic.FixesByModel)
                {
                    BarSeries.Points.AddXY(kvp.Key.ValidStart.ToOADate(), kvp.Value[Category]);
                    if (a == DrawParams.Length - 2)
                    {
                        BarSeries.Points[BarSeries.Points.Count - 1].Label = kvp.Key.FullClass.Replace("CIM_", "").Replace('_', '.');
                        BarSeries.Points[BarSeries.Points.Count - 1].LabelAngle = 90;
                    }
                }
            }
            return StackedBar;
        }

        /// <summary>
        /// Generate a new time series graph
        /// </summary>
        /// <param name="NewDic"></param>
        /// <param name="GraphMeasure"></param>
        /// <param name="Title"></param>
        /// <param name="Width"></param>
        /// <param name="Height"></param>
        /// <returns></returns>
        /// <param name="SummaryMeasure"></param>
        /// <param name="MaxDate"></param>
        /// <param name="MinDate"></param>
        private static Chart GenerateTimeSeries(TallyDictionary NewDic, TallyDictionary.enumOneLineCategory GraphMeasure, TallyDictionary.enumOneLineCategory SummaryMeasure, String Title, int Width, int Height, DateTime MinDate, DateTime MaxDate)
        {
            Chart OutChart = new Chart();
            OutChart.Palette = ChartColorPalette.Bright;
            OutChart.Size = new Size(Width, Height);
            OutChart.Series.Clear();
            OutChart.ChartAreas.Add(new ChartArea());

            OutChart.ChartAreas[0].Area3DStyle.Enable3D = true;
            OutChart.ChartAreas[0].Area3DStyle.IsRightAngleAxes = false;
            OutChart.ChartAreas[0].Area3DStyle.Inclination = 40;
            OutChart.ChartAreas[0].Area3DStyle.Rotation = 20;
            OutChart.ChartAreas[0].Area3DStyle.LightStyle = LightStyle.Realistic;
            OutChart.Titles.Add(Title);


            /* OutChart.ChartAreas[0].Axes[0].LineDashStyle = ChartDashStyle.Dot;
             OutChart.ChartAreas[0].Axes[0].MajorGrid.LineDashStyle = ChartDashStyle.Dot;
             OutChart.ChartAreas[0].Axes[0].MinorGrid.LineDashStyle = ChartDashStyle.Dot;
             OutChart.ChartAreas[0].Axes[0].MajorTickMark.LineDashStyle = ChartDashStyle.Dot;
             OutChart.ChartAreas[0].Axes[0].MinorTickMark.LineDashStyle = ChartDashStyle.Dot;*/

            Legend lgd = new Legend();
            OutChart.Legends.Add(lgd);

            foreach (KeyValuePair<MM_Database_User, SortedDictionary<DateTime, Dictionary<TallyDictionary.enumOneLineCategory, int>>> kvp in NewDic.FixesByDate)
                if (NewDic.FixesByUser[kvp.Key][TallyDictionary.enumOneLineCategory.Repaired_Fixes_Total] > 0 && !kvp.Key.Equals(NewDic.Total))
                {
                    Series NewSeries = OutChart.Series.Add(kvp.Key.FullName + " (" + NewDic.FixesByUser[kvp.Key][SummaryMeasure].ToString("#,##0") + ")");
                    NewSeries.XValueType = ChartValueType.DateTime;
                    NewSeries.ChartType = SeriesChartType.Line;
                    int CurValue = 0;

                    if (!kvp.Value.ContainsKey(MinDate))
                        NewSeries.Points.AddXY(MinDate.ToOADate(), 0);

                    foreach (KeyValuePair<DateTime, Dictionary<TallyDictionary.enumOneLineCategory, int>> kvp2 in kvp.Value)
                    {
                        CurValue += kvp2.Value[GraphMeasure];
                        NewSeries.Points.AddXY(kvp2.Key.ToOADate(), CurValue);
                    }
                    if (!kvp.Value.ContainsKey(MaxDate))
                        NewSeries.Points.AddXY(MaxDate.ToOADate(), CurValue);
                }

            return OutChart;
        }

        /// <summary>
        /// Generate a pie chart
        /// </summary>
        /// <param name="NewDic"></param>
        /// <param name="GraphMeasure"></param>
        /// <param name="OverallMeasure"></param>
        /// <param name="Title"></param>
        /// <param name="Width"></param>
        /// <param name="Height"></param>
        /// <returns></returns>
        private static Chart GeneratePieChart(TallyDictionary NewDic, TallyDictionary.enumOneLineCategory GraphMeasure, TallyDictionary.enumOneLineCategory OverallMeasure, string Title, int Width, int Height)
        {
            Chart OutChart = new Chart();
            OutChart.Palette = ChartColorPalette.Bright;
            OutChart.Size = new Size(Width, Height);
            OutChart.Series.Clear();
            OutChart.ChartAreas.Add(new ChartArea());
            OutChart.Titles.Add(Title);

            //Add in our pie chart
            Series PieSeries = new Series();
            PieSeries.Palette = ChartColorPalette.Bright;
            PieSeries.ChartType = SeriesChartType.Doughnut;
            PieSeries["PieLabelStyle"] = "Outside";
            OutChart.ChartAreas[0].Area3DStyle.Enable3D = true;
            PieSeries.SmartLabelStyle.AllowOutsidePlotArea = LabelOutsidePlotAreaStyle.Yes;
            OutChart.Series.Add(PieSeries);

            foreach (KeyValuePair<MM_Database_User, Dictionary<TallyDictionary.enumOneLineCategory, int>> kvp in NewDic.FixesByUser)
                if (kvp.Value[TallyDictionary.enumOneLineCategory.Repaired_Fixes_Total] > 0 && !kvp.Key.Equals(NewDic.Total))
                {
                    DataPoint dpNew = new DataPoint(0, kvp.Value[GraphMeasure]);
                    dpNew.Label = kvp.Key.FullName + " ( " + kvp.Value[GraphMeasure].ToString("#,##0") + ")";
                    PieSeries.Points.Add(dpNew);
                }

            //Determine our incomplete number
            int Incomplete = NewDic.FixesByUser[NewDic.Total][OverallMeasure] - NewDic.FixesByUser[NewDic.Total][GraphMeasure];
            if (Incomplete > 0)
            {
                DataPoint dpIncomplete = new DataPoint(0, Incomplete);
                dpIncomplete.CustomProperties = "Exploded=True, PieLabelStyle=Outside";
                dpIncomplete.Label = "Incomplete (" + Incomplete.ToString("#,##0") + ")";
                PieSeries.Points.Add(dpIncomplete);
            }
            return OutChart;
        }
        #endregion

    }
}